﻿using System;
using System.Text.RegularExpressions;
using System.Web;
using System.Linq;
using System.Globalization;
using System.Collections.Generic;
using System.IO;
using System.Drawing;
using System.Text;


namespace MplusM.MovieDatabaseWorkers
{

    public class IMDbWorker : IMovieDatabaseWorker
    {
        private string url;
        private Encoding encoding;
        public event WorkerContentHandler ContentLoaded;
        public event WorkerErrorHandler ContentLoadedFailed;
        public event WorkerImageHandler CoverLoaded;
        public event WorkerErrorHandler CoverLoadedFailed;

        public IMDbWorker()
        {
            this.url = "";
            encoding = Encoding.UTF8;
        }

        public IMDbWorker(string url)
            : this()
        {
            this.url = url;
        }

        public string Url
        {
            get
            {
                return url;
            }
            set
            {
                url = value;
            }
        }

        public Encoding Encoding
        {
            get
            {
                return encoding;
            }
            set
            {
                encoding = value;
            }
        }

        #region public methods
		/// <summary>
		/// loads contents from imdb asynchronously in a new thread
		/// </summary>
        public void LoadContentAsync()
        {
            System.Threading.Thread t = new System.Threading.Thread(getContent);
            t.Start(this.url);
        }
		/// <summary>
		/// loads cover from imdb asynchronously in a new thread
		/// </summary>
        public void LoadCoverAsync()
        {
            System.Threading.Thread t = new System.Threading.Thread(getCover);
            t.Start(this.url);
        }

        #endregion

        #region public static methods
		/// <summary>
		/// creates a search URL onto which the content should be placed for the given title
		/// </summary>
		/// <param name="searchTitle">the that is searched for</param>
		/// <returns>an URL of a given title</returns>
        public static String CreateSearchUrl(String searchTitle)
        {
            return "http://www.imdb.com/find?q=" + searchTitle.Replace(" ", "+") + "&s=tt";
        }

		/// <summary>
		/// returns an URL of a cover parsed from the content obtained for some media title
		/// </summary>
		/// <param name="content">the content of a certain media</param>
		/// <returns>string containing an URL of a cover</returns>
        public static String GetCoverURL(String content)
        {
            int indexImage = content.IndexOf("img_primary");
            if (indexImage < 0)
            {
                return null;
            }
            int lengthImage = content.IndexOf("style", indexImage) - indexImage;
            if (lengthImage <= 0)
            {
                return null;
            }
            String subContent = content.Substring(indexImage, lengthImage);
            indexImage = subContent.IndexOf("src=\"");
            if (indexImage < 0)
            {
                return null;
            }
            indexImage += 4;
            subContent = subContent.Substring(indexImage).Trim();
            return subContent.Replace("\"", "");
        }

        /// <summary>
        /// parses the web content of a certain media and transforms it into the
		/// <c>MovingPicture</c> Class representing the media
        /// </summary>
		/// <remarks>
		/// \p{L} IS UNICODE LETTER
		/// </remarks>
        /// <param name="content">the web page content as a <c>String</c> class</param>
        /// <returns>a <c>MovingPicture</c> class representing the found media</returns>
        public static MovingPicture GetMovieDetails(String content)
        {
            MovingPicture result = new MovingPicture();
            Regex reg = null;
            // originalName or translatedName
            reg = new Regex("(original title)");
            if (reg.IsMatch(content))
            {
                //originalName
                reg = new Regex(@">[\p{L}\s:-]*<[a-zA-Z]+>\(original title\)</[a-zA-Z]+>");
                if (reg.IsMatch(content))
                {
                    Match match = reg.Match(content);
                    reg = new Regex(@">|<[a-zA-Z]+|</[a-zA-Z]+|\(original title\)");
                    result.OriginalName = reg.Replace(match.Value, "").Trim();
                }
                //translatedName
                reg = new Regex("itemprop=\"name\">[\\p{L}\\s:-]*<");
                if (reg.IsMatch(HttpUtility.HtmlDecode(content)))
                {
                    Match match = reg.Match(HttpUtility.HtmlDecode(content));
                    reg = new Regex("itemprop=\"name\">|<");
                    result.TranslatedName = reg.Replace(match.Value, "").Trim();
                }
            }
            else
            {
                reg = new Regex(@"<title>[\p{L}\s:-]* \([0-9]{4}\) - IMDb</title>");
                if (reg.IsMatch(content))
                {
                    Match match = reg.Match(content);
                    reg = new Regex(@"IMDb| - |<title>|</title>|\([0-9]{4}\)");
                    result.OriginalName = reg.Replace(match.Value, "").Trim();
                }
            }
            // length
            reg = new Regex(@">[0-9]+ min</time>");
            if (reg.IsMatch(content))
            {
                Match match = reg.Match(content);
                reg = new Regex(@">|min</time>");
                ushort lengthOfMovie;
                if (ushort.TryParse(reg.Replace(match.Value, "").Trim(), out lengthOfMovie))
                {
                    result.Length = lengthOfMovie;
                }
            }
            // country
            reg = new Regex("href=\"/country/[a-zA-Z]+\"\\s*>[\\p{L}\\s ]*</a>");
            MatchCollection matches = reg.Matches(content);
            for (int i = 0; i < matches.Count; i++)
            {
                reg = new Regex("href=\"/country/[a-zA-Z]+\"\\s*>|</a>");
                result.Country += reg.Replace(matches[i].Value, "") + (i == matches.Count - 1 ? "" : ", ");
            }
            // director
            reg = new Regex("itemprop=\"director\"\\s*>[\\p{L}\\s ]*</a>");
            matches = reg.Matches(content);
            for (int i = 0; i < matches.Count; i++)
            {
                reg = new Regex("itemprop=\"director\"\\s*>|</a>");
                result.Director += reg.Replace(matches[i].Value, "") + (i == matches.Count - 1 ? "" : ", ");
            }
            // content
            int indexContent = content.IndexOf("<h2>Storyline</h2>");
            if (indexContent >= 0)
            {
                int lengthContent = content.IndexOf("<em", indexContent) - indexContent;
                if (lengthContent > 0)
                {
                    String tmpContent = content.Substring(indexContent, lengthContent);
                    reg = new Regex("<h2>Storyline</h2>\\s*<p>|<em");
                    result.Content = reg.Replace(HttpUtility.HtmlDecode(tmpContent), "").Trim();
                }
            }
            // release date
            reg = new Regex("<time itemprop=\"datePublished\" datetime=\"[0-9999]+-[0-9]+[0-9]+-[0-9]+[0-9]+\">");
            if (reg.IsMatch(content))
            {
                Match match = reg.Match(content);
                reg = new Regex("<time itemprop=\"datePublished\" datetime=\"|\">");
                DateTime release;
                if (DateTime.TryParse(reg.Replace(match.Value, ""), new CultureInfo("en-us"), DateTimeStyles.AssumeUniversal, out release))
                {
                    result.ReleaseDate = release;
                }
            }
            // rating
            reg = new Regex("<span itemprop=\"ratingValue\">[0-9]+.[0-9]+</span>");
            if (reg.IsMatch(content))
            {
                Match match = reg.Match(content);
                reg = new Regex("<span itemprop=\"ratingValue\">|</span>");
                double rating;
                if (Double.TryParse(reg.Replace(match.Value, ""), NumberStyles.AllowDecimalPoint, new CultureInfo("en-us"), out rating))
                {
                    result.Rating = rating;
                }
            }
            // genres
            reg = new Regex("itemprop=\"genre\"\\s*>[\\p{L}\\s ]*</a>");
            if (reg.IsMatch(content))
            {
                result.Genres = new List<string>();
                foreach (Match match in reg.Matches(content))
                {
                    reg = new Regex("itemprop=\"genre\"\\s*>|</a>");
                    result.Genres.Add(reg.Replace(match.Value, ""));
                }
            }
            // cast
            reg = new Regex("href=\"/name/nm[0-9]+/\"\\s*>[\\p{L}\\s ]*</a>\\s*</td>");
            if (reg.IsMatch(content))
            {
                result.Cast = new List<string>();
                foreach (Match match in reg.Matches(content))
                {
                    reg = new Regex("href=\"/name/nm[0-9]+/\"\\s*>|</a>|\\s*</td>");
                    result.Cast.Add(reg.Replace(match.Value, ""));
                }
            }
            return result;
        }

		/// <summary>
		/// Gets popular titles from the given content
		/// </summary>
		/// <param name="content">a content of a certain media</param>
		/// <returns>Dictionary of popular titles</returns>
        public static Dictionary<String, String> GetPopularTitles(String content)
        {
            int indexPopular = content.IndexOf("<p><b>Popular Titles");
            int length = content.IndexOf("<p><b>Titles (") - indexPopular;
            if (indexPopular < 0 || length <= 0)
            {
                return null;
            }
            String popular = content.Substring(indexPopular, length);
            return getTitles(popular);
        }

		/// <summary>
		/// Obtains exact titles from the given web content passed as a <c>String</c> argument
		/// </summary>
		/// <param name="content">a string containing the web content of a certain media</param>
		/// <returns>Dictionary of exact matches with the given title</returns>
        public static Dictionary<String, String> GetExactTitles(String content)
        {
            int indexExact = content.IndexOf("Titles (Exact Matches)");
            if (indexExact < 0)
            {
                return null;
            }
            String exact = content.Substring(indexExact);
            int length = exact.IndexOf("<p><b>Titles (");
            if (length <= 0)
            {
                return null;
            }
            String result = exact.Substring(0, length);
            return getTitles(result);
        }

		/// <summary>
		/// retrieves all the results from the search web result that matches the searched
		/// title at least partially
		/// </summary>
		/// <param name="content">imdb web search result</param>
		/// <returns>a <c>Dictionary</c> containing all the partial titles results as strings</returns>
        public static Dictionary<String, String> GetPartialTitles(String content)
        {
            int indexPartial = content.IndexOf("Titles (Partial Matches)");
            if (indexPartial < 0)
            {
                return null;
            }
            String result = content.Substring(indexPartial);
            return getTitles(result);
        }

        #endregion

        #region private static methods
		/// <summary>
		/// retrieves the media titles from the web content
		/// </summary>
		/// <param name="content">web content of a certain media</param>
		/// <returns></returns>
        private static Dictionary<String, String> getTitles(String content)
        {
            Regex reg = new Regex(@"</a> \([0-9]{4}\)");
            MatchCollection matches = reg.Matches(content);
            Dictionary<String, String> ret = new Dictionary<String, String>();
            foreach (Match itm in matches)
            {
                String tmp = "";
                String link = "";
                String name = "";
                String tmpContent = content;
                int indexValue = tmpContent.IndexOf(itm.Value);
                while (indexValue > 0)
                {
                    tmp = tmpContent.Substring(0, indexValue);
                    int indexLink = tmp.LastIndexOf("=/");
                    if (indexLink < 0)
                    {
                        break;
                    }
                    int lengthLink = tmp.LastIndexOf("/") - tmp.LastIndexOf("=/");
                    if (lengthLink <= 0)
                    {
                        break;
                    }
                    link = tmp.Substring(indexLink + 1, lengthLink);
                    int indexName = tmp.LastIndexOf(">");
                    if (indexName < 0)
                    {
                        break;
                    }
                    int indexYear = itm.Value.IndexOf("(");
                    if (indexYear < 0)
                    {
                        break;
                    }
                    name = tmp.Substring(indexName + 1) + " " + itm.Value.Substring(indexYear);
                    reg = new Regex("/title/tt[0-9]+/");
                    if (!ret.ContainsKey("http://www.imdb.com" + link) && String.IsNullOrEmpty(reg.Replace(link, "")))
                    {
                        ret.Add("http://www.imdb.com" + link, HttpUtility.HtmlDecode(name));
                    }
                    tmpContent = tmpContent.Substring(tmpContent.IndexOf(itm.Value) + itm.Value.Length);
                    indexValue = tmpContent.IndexOf(itm.Value);
                }
            }
            return ret.Count == 0 ? null : ret;
        }

        #endregion

        #region private methods
		/// <summary>
		/// retrieves a web content from the given url and triggers appropriate events to revive result at the GUI
		/// when the asynchronous queries are complete
		/// </summary>
		/// <param name="url"><value>String</value> url of a certain web content to be analyzed</param>
        private void getContent(object url)
        {
            System.Net.WebClient webClient = new System.Net.WebClient();
            webClient.Encoding = encoding;
            String result = null;
            try
            {
                result = webClient.DownloadString((String)url);
            }
            catch (Exception ex)
            {
                onContentLoadedFailed(ex);
            }
            finally
            {
                webClient.Dispose();
            }
            if (result != null) onContentLoaded(result);
        }

		/// <summary>
		/// retrieves a cover <c>Image</c> from a given URL
		/// </summary>
		/// <param name="url"><c>String</c> url at which the <c>Image</c> is stored</param>
        private void getCover(object url)
        {
            System.Net.WebClient webClient = new System.Net.WebClient();
            Image img = null;
            try
            {
                byte[] data = webClient.DownloadData((String)url);
                img = Image.FromStream(new MemoryStream(data));
            }
            catch (Exception ex)
            {
                onCoverLoadedFailed(ex);
            }
            finally
            {
                webClient.Dispose();
            }
            if (img != null) onCoverLoaded(img);
        }

		/// <summary>
		/// an action to be triggered when the content is loaded
		/// </summary>
		/// <param name="content"></param>
        private void onContentLoaded(String content)
        {
            if (ContentLoaded != null)
            {
                ContentLoaded(content);
            }
        }

		/// <summary>
		/// an action to be triggered when the content loading fails
		/// </summary>
		/// <param name="ex"></param>
        private void onContentLoadedFailed(Exception ex)
        {
            if (ContentLoadedFailed != null)
            {
                ContentLoadedFailed(ex);
            }
        }

		/// <summary>
		/// an action to be triggered when the Cover is loaded
		/// </summary>
		/// <param name="img"></param>
        private void onCoverLoaded(Image img)
        {
            if (CoverLoaded != null)
            {
                CoverLoaded(img);
            }
        }

		/// <summary>
		/// an action to be triggered when the Cover loading fails
		/// </summary>
		/// <param name="ex"></param>
        private void onCoverLoadedFailed(Exception ex)
        {
            if (CoverLoadedFailed != null)
            {
                CoverLoadedFailed(ex);
            }
        }

        #endregion
    }


}

